﻿using System;
using System.IO;
using System.Drawing;
using System.Collections;
using System.Text;
using ui = System.ComponentModel;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Bam.EventObservation;
using System.Xml;
using Microsoft.BizTalk.Streaming;
using System.Collections.Generic;
using System.Linq;

namespace BizTalk.ESB.Extensions.PipelineComponents
{
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent),
     ComponentCategory(CategoryTypes.CATID_Any)]
    public class EsbBamLogMessage : IBaseComponent, IComponent, IComponentUI, IPersistPropertyBag
    {
        #region PrivateFields

        private const string DescriptionProperty = "Custom Pipeline Component to log messages as they pass through";
        private const string NameProperty = "BizTalk.ESB.Extensions.PipelineComponent.EsbBamLogMessage";
        private const string VersionProperty = "1.0";

        //ItineraryCustomMessage Property Names
        private const string BamActivityName = "ItineraryCustomMessage";
        private const string ItineraryHeaderProperty = "ItineraryHeader";
        private const string ItineraryuuidXPathProperty = "ItineraryuuidXPath";
        private const string ItineraryNameXPathProperty = "ItineraryNameXPath";
        private const string MaskXPathProperty = "MaskXPath";
        private const string MessageIdProperty = "MessageId";
        private const string MessageNameProperty = "MessageName";
        private const string EnabledProperty = "Enabled";
        private const string ReferenceType = "Message";
        private const string ReferenceName = "BizTalk.ESB.Extensions.PipelineComponent.EsbBamLogMessage";
        private const string ItineraryNamespace = "http://schemas.microsoft.biztalk.practices.esb.com/itinerary/system-properties";
        private const string BTSPropertyNamespace = "http://schemas.microsoft.com/BizTalk/2003/system-properties";

        private bool _Enabled = true;


        #endregion

        #region PublicProperties

        [ui.TypeConverter(typeof(ContextPropertyTypeConverter))]
        public bool Enabled { get { return _Enabled; } set { _Enabled = value; } }
        public ContextProperty ItineraryHeader { get; set; }
        public String ItineraryuuidXPath { get; set; }
        public String ItineraryNameXPath { get; set; }
        public String MaskXPath { get; set; }

        #endregion

        #region IBaseComponent

        public string Description
        {
            get
            {
                return DescriptionProperty;
            }
        }

        public string Name
        {
            get
            {
                return NameProperty;
            }
        }

        public string Version
        {
            get
            {
                return VersionProperty;
            }
        }

        #endregion

        #region IComponent

        public IBaseMessage Execute(IPipelineContext pipeLineContext, IBaseMessage baseMessage)
        {
            if (Enabled)
            {
                var bodyPart = baseMessage.BodyPart;
                XmlDocument MaskXmlDocument = new XmlDocument();
                List<string> MaskStrings;


                if (bodyPart != null)
                {
                    //new up an activity and event stream
                    var activityId = Guid.NewGuid().ToString();
                    String interchangeID;
                    
                    EventStream eventStream = pipeLineContext.GetEventStream();

                    if (baseMessage.Context.Read("InterchangeID", "http://schemas.microsoft.com/BizTalk/2003/system-properties") != null)
                        interchangeID = Guid.Parse(baseMessage.Context.Read("InterchangeID", "http://schemas.microsoft.com/BizTalk/2003/system-properties").ToString()).ToString();
                    else
                        interchangeID = "";

                    #region extract message context info

                    //extract message context info
                    var itineraryHeader = ExtractMessageInfo(this.ItineraryHeader, baseMessage);
                    string itineraryuuid, itineraryName, messageType; //, serviceIdGuid, serviceuuid;
                    if (itineraryHeader.Length > 0)
                    {
                        var ItineraryXML = new System.Xml.XmlDocument();
                        ItineraryXML.LoadXml(itineraryHeader);

                        itineraryuuid = ItineraryXML.SelectSingleNode(ItineraryuuidXPath).InnerText;
                        baseMessage.Context.Write("ItineraryGuid", ItineraryNamespace, itineraryuuid);

                        itineraryName = ItineraryXML.SelectSingleNode(ItineraryNameXPath).InnerText;
                        baseMessage.Context.Write("ItineraryName", ItineraryNamespace, itineraryName);

                        //a = ItineraryXML.SelectSingleNode(ServiceIdGuidXPath).InnerText;

                        //serviceuuid = ItineraryXML.SelectSingleNode(ServiceuuidXPath).InnerText;
                    }
                    else
                    {
                        if (baseMessage.Context.Read("ItineraryGuid", ItineraryNamespace) != null)
                            itineraryuuid = baseMessage.Context.Read("ItineraryGuid", ItineraryNamespace).ToString();
                        else itineraryuuid = "";
                        if (baseMessage.Context.Read("ItineraryName", ItineraryNamespace) != null)
                            itineraryName = baseMessage.Context.Read("ItineraryName", ItineraryNamespace).ToString();
                        else itineraryName = "";
                        //serviceIdGuid = "";
                        //serviceuuid = "";
                    }

                    #endregion


                    //extract message
                    var message = new StringBuilder(); //Save message in this StringBuilder
                    var copyStream = new MemoryStream(); //Copy the original stream to the a new stream 
                    var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
                    ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(baseMessage.BodyPart.GetOriginalDataStream());

                    //Get the messageType from the rootnode#namespace
                    XmlTextReader xmlReader;
                    if (baseMessage.Context.Read("MessageType", BTSPropertyNamespace) != null)
                        messageType = baseMessage.Context.Read("MessageType", BTSPropertyNamespace).ToString();
                    else try
                        {
                            xmlReader = new System.Xml.XmlTextReader(seekableStream);
                            string rootNode = "", nameSpace = "";
                            xmlReader.MoveToContent();
                            xmlReader.MoveToElement();
                            rootNode = xmlReader.LocalName;
                            nameSpace = xmlReader.NamespaceURI;
                            messageType = nameSpace + "#" + rootNode.Substring(rootNode.IndexOf(":") + 1);
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Trace.WriteLine("EsbBamLogMessage-Error :: " + ex.Message);
                            messageType = "unknown";
                        }
                    //reset stream
                    seekableStream.Seek(0, SeekOrigin.Begin);

                    //Read OriginalStream
                    using (StreamReader streamReader = new StreamReader(seekableStream))
                    {

                        var line = string.Empty; //Read Stream Line by Line
                        while ((line = streamReader.ReadLine()) != null)
                        {
                            message.AppendLine(line); //Save Message to StringBuilder
                            copyStreamWriter.WriteLine(line); //Copy to new stream
                        }

                        copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
                        copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
                        baseMessage.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

                        pipeLineContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
                        pipeLineContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

                    }

                    //mask any data we don't want to save in the message as it comes across the wire
                    if (MaskXPath != null && MaskXPath != string.Empty)
                    {
                        MaskStrings = MaskXPath.Split('|').Select(data => data).ToList();
                        //attempt to mask sensitive data
                        if (MaskStrings.Count > 0)
                        {
                            MaskXmlDocument.LoadXml(message.ToString());
                            foreach (string t in MaskStrings)
                            {
                                foreach (XmlNode node in MaskXmlDocument.SelectNodes(t))
                                {
                                    node.InnerXml = new string('X', node.InnerXml.Length);
                                }
                            }

                            message.Clear();
                            message.Append(MaskXmlDocument.OuterXml.ToString());
                        }

                    }

                    //log the message
                    eventStream.BeginActivity(BamActivityName,
                                              activityId);

                    eventStream.UpdateActivity(BamActivityName,
                                               activityId,
                                               "Itineraryuuid", itineraryuuid,
                                               "ItineraryName", itineraryName,
                                               MessageIdProperty, baseMessage.MessageID.ToString(),
                                               MessageNameProperty, messageType,
                                               "InterchangeId", interchangeID);//,
                    //"ServiceIdGuid", serviceIdGuid,
                    //"Serviceuuid", serviceuuid);

                    eventStream.AddReference(BamActivityName,
                                             activityId,
                                             ReferenceType,
                                             ReferenceName,
                                             DateTime.Now.ToString(),
                                             message.ToString());


                    eventStream.EndActivity(BamActivityName,
                                            activityId);

                    eventStream.Flush();
                }
            }
            return baseMessage;
        }

        #endregion

        #region IComponentUI

        public IntPtr Icon
        {
            get
            {
                return new IntPtr();
            }
            //return ((Bitmap)resManager.GetObject("FixMsgBitmap")).GetHicon();
        }

        public IEnumerator Validate(object projectSystem)
        {
            IEnumerator enumerator = null;

            return enumerator;
        }

        #endregion

        #region IPersistPropertyBag

        public void GetClassID(out Guid classId)
        {
            classId = new System.Guid("9510A448-FF7C-480D-89ED-B34AF6BF9344");
        }

        public void InitNew()
        {

        }

        public void Load(IPropertyBag propertyBag, int errorLog)
        {
            var val = string.Empty;

            //Log Message Body
            object enabled = ReadPropertyBag(propertyBag, EnabledProperty);
            if (enabled != null)
                Enabled = (bool)enabled;

            //ItineraryHeader
            if (this.ItineraryHeader == null)
            {
                val = (string)ReadPropertyBag(propertyBag,
                                                  ItineraryHeaderProperty);

                this.ItineraryHeader = ConvertToContextProperty(val);
            }

            //Itineraryuuid
            if (this.ItineraryuuidXPath == null)
            {
                val = (string)ReadPropertyBag(propertyBag,
                                                  ItineraryuuidXPathProperty);

                this.ItineraryuuidXPath = val;
            }

            //ItineraryName
            if (this.ItineraryNameXPath == null)
            {
                val = (string)ReadPropertyBag(propertyBag,
                                              ItineraryNameXPathProperty);

                this.ItineraryNameXPath = val;
            }

            //MaskXPath
            if (this.MaskXPath == null)
            {
                val = (string)ReadPropertyBag(propertyBag, MaskXPathProperty);
                this.MaskXPath = val;
            }

        }

        public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
        {

            //Log Message Body
            bool bln = this.Enabled;
            WritePropertyBag(propertyBag, EnabledProperty, bln);

            //ItineraryHeader
            var val = (object)ConvertToString(this.ItineraryHeader);
            WritePropertyBag(propertyBag,
                             ItineraryHeaderProperty,
                             val);

            //Itineraryuuid
            val = (object)this.ItineraryuuidXPath;
            WritePropertyBag(propertyBag,
                             ItineraryuuidXPathProperty,
                             val);

            //ItineraryName
            val = (object)this.ItineraryNameXPath;
            WritePropertyBag(propertyBag,
                             ItineraryNameXPathProperty,
                             val);

            //MaskXPath
            val = (object)this.MaskXPath;
            WritePropertyBag(propertyBag, MaskXPathProperty, val);

        }

        #endregion

        #region CustomMethods

        /// <summary>
        /// Reads property value from property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <returns>Value of the property.</returns>
        private static object ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
        {
            object val = null;
            try
            {
                pb.Read(propName, out val, 0);
            }

            catch (ArgumentException)
            {
                return val;
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message);
            }
            return val;
        }

        /// <summary>
        /// Writes property values into a property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <param name="val">Value of property.</param>
        private static void WritePropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, object val)
        {
            try
            {
                pb.Write(propName, ref val);
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message);
            }
        }

        private static ContextProperty ConvertToContextProperty(string contextPropertyString)
        {
            if (!string.IsNullOrEmpty(contextPropertyString))
            {
                //Parse the format "PropertyName, PropertyNamespace"
                int comma = contextPropertyString.IndexOf(',');
                if (comma != -1)
                {
                    string[] contextPropertyStrings = contextPropertyString.Split(',');

                    if (contextPropertyStrings.Length == 2)
                    {
                        var contextProperty = new ContextProperty();
                        contextProperty.PropertyName = contextPropertyStrings[0];
                        contextProperty.PropertyNamespace = contextPropertyStrings[1];

                        return contextProperty;
                    }
                }
            }

            return new ContextProperty()
            {
                PropertyName = string.Empty,
                PropertyNamespace = string.Empty
            };
        }

        private static string ConvertToString(ContextProperty contextProperty)
        {
            //Simply build the string
            if (string.IsNullOrEmpty(contextProperty.PropertyName) && string.IsNullOrEmpty(contextProperty.PropertyNamespace))
                return string.Empty;

            return contextProperty.PropertyName + "," + contextProperty.PropertyNamespace;
        }

        private static string ExtractMessageInfo(ContextProperty contextProperty, IBaseMessage baseMessage)
        {
            var contextValue = (string)baseMessage.Context.Read(contextProperty.PropertyName,
                                                                contextProperty.PropertyNamespace);

            if (string.IsNullOrEmpty(contextValue))
                contextValue = string.Empty;


            return contextValue;
        }

        #endregion
    }
}
